package com.pingan.haofang.searchcloud.index.register;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.pingan.haofang.framework.common.functional.lang.control.base.Enums;
import com.pingan.haofang.framework.common.functional.lang.data.Try;
import com.pingan.haofang.framework.common.functional.lang.data.Tuple2;
import com.pingan.haofang.searchcloud.api.matedata.FieldMeta;
import com.pingan.haofang.searchcloud.api.matedata.IndexMeta;
import com.pingan.haofang.searchcloud.api.matedata.IndexMetadataHolder;
import com.pingan.haofang.searchcloud.api.matedata.constant.MetadataFieldType;
import com.pingan.haofang.searchcloud.collection.service.CollectionService;
import com.pingan.haofang.searchcloud.common.exception.SystemException;
import com.pingan.haofang.searchcloud.index.dto.IndexInfoQueryResult;
import com.pingan.haofang.searchcloud.index.service.IndexService;
import com.pingan.haofang.searchcloud.metadata.dto.MetadataFieldQueryResult;
import com.pingan.haofang.searchcloud.metadata.service.MetadataService;

/**
 * 索引facade接口
 *
 * @author baojie371
 * @date 17/5/8
 * @since 1.0.0
 */
@Service
public class IndexMetadataRegister implements IndexMetadataHolder, ApplicationContextAware {

    private static ApplicationContext applicationContext;

    private static final int MAX_NUM = 10 * 1024;

    private static final int MAX_TIME_MINUTES = 3 * 10;

    private final LoadingCache<String, IndexMeta> metaCache = CacheBuilder.newBuilder()
            .maximumSize(MAX_NUM)
            .expireAfterWrite(MAX_TIME_MINUTES, TimeUnit.MINUTES)
            .build(new CacheLoader<String, IndexMeta>() {
                @Override
                public IndexMeta load(String key) throws Exception {
                    return findIndexMetaByIndexName(key);
                }
            });

    private final LoadingCache<Tuple2<String, String>, IndexMeta> metaCollectionCache = CacheBuilder.newBuilder()
            .maximumSize(MAX_NUM)
            .expireAfterWrite(MAX_TIME_MINUTES, TimeUnit.MINUTES)
            .build(new CacheLoader<Tuple2<String, String>, IndexMeta>() {
                @Override
                public IndexMeta load(Tuple2<String, String> key) throws Exception {
                    return findIndexMetaByIndexNameAndCollectionName(key.t1(), key.t2());
                }
            });

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        IndexMetadataRegister.applicationContext = applicationContext;
    }

    private IndexService indexService;

    private CollectionService collectionService;

    private MetadataService metadataService;

    private void initIndexService() {
        if (null == indexService) {
            indexService = applicationContext.getBean(IndexService.class);
        }
    }

    private void initCollectionService() {
        if (null == collectionService) {
            collectionService = applicationContext.getBean(CollectionService.class);
        }
    }

    private void initMetadataService() {
        if (null == metadataService) {
            metadataService = applicationContext.getBean(MetadataService.class);
        }
    }

    @Override
    public Optional<FieldMeta> findFiledMeta(String indexName, String fieldName) {
        initIndexService();
        Objects.requireNonNull(indexName, "indexName is null");
        Objects.requireNonNull(fieldName, "fieldName is null");
        return Optional.ofNullable(findIndexMeta(indexName).getFiledMetas().get(fieldName));
    }

    @Override
    public Optional<String> findPKFieldName(String indexName) {

        Objects.requireNonNull(indexName, "indexName is null");
        initIndexService();

        Map<String, FieldMeta> filedMetaMap = Try.of(() -> metaCache.get(indexName).getFiledMetas())
                .orElseThrow(RuntimeException::new);

        for (Map.Entry<String, FieldMeta> keyValue : filedMetaMap.entrySet()) {
            String key = keyValue.getKey();
            FieldMeta fieldMeta = keyValue.getValue();
            if (fieldMeta.isPk()) {
                return Optional.of(key);
            }
        }

        return Optional.empty();
    }

    @Override
    public IndexMeta findIndexMeta(String indexName) {
        Objects.requireNonNull(indexName, "indexName is null");
        initIndexService();
        try {
            return metaCache.get(indexName);
        } catch (Exception e) {
            throw new SystemException("search.indexname.notexist", new String[]{indexName});
        }
    }

    public IndexMeta findIndexMetaByCollection(String projectCode, String indexName, String collectionName) {
        Objects.requireNonNull(projectCode, "projectCode is null");
        Objects.requireNonNull(indexName, "indexName is null");
        Objects.requireNonNull(collectionName, "collectionName is null");

        initIndexService();
        return Try.of(() -> metaCollectionCache.get(Tuple2.of(indexName, collectionName)))
                .orElseThrow(e -> new RuntimeException(
                        String.format("Find Index Meta Failed, indexName: %s, collectionName: %s",
                                indexName, collectionName), e));
    }

    /**
     * 清理过期的Schema和Collection
     *
     * @param topN
     */
    public void clearOldConfigAndCollection(String indexName, int topN) {

        initIndexService();
        IndexInfoQueryResult index = indexService.queryByIndexNameOfStorage(indexName);

        initCollectionService();
        List<Long> collectionIds = collectionService.clearOldCollectionsOfStorage(index.getBasicInfo().getId(),
                index.getBasicInfo().getEngineType(), topN);
        collectionService.clearOldCollectionsOfDb(collectionIds);

        initMetadataService();
        List<Long> metadataIds = metadataService.clearOldConfigOfStorage(index.getBasicInfo().getId(),
                index.getBasicInfo().getEngineType(), topN);
        metadataService.clearOldConfigOfDb(metadataIds);
    }

    private IndexMeta findIndexMetaByIndexName(String indexName) {
        IndexInfoQueryResult indexInfo = indexService.queryByIndexNameOfStorage(indexName);
        if (null == indexInfo) {
            return null;
        }
        return indexInfo2IndexMeta(indexInfo);
    }

    private IndexMeta findIndexMetaByIndexNameAndCollectionName(String indexName, String collectionName) {
        IndexInfoQueryResult indexInfo =
                indexService.queryByIndexNameAndCollectionNameOfStorage(indexName, collectionName);
        return indexInfo2IndexMeta(indexInfo);
    }

    private IndexMeta indexInfo2IndexMeta(IndexInfoQueryResult indexInfo) {
        IndexMeta index = new IndexMeta();
        index.setIndexName(indexInfo.getBasicInfo().getIndexName());
        index.setHost(indexInfo.getBasicInfo().getHost());
        index.setEngineType(indexInfo.getBasicInfo().getEngineType());
        index.setShardCount(indexInfo.getBasicInfo().getShardCount());
        index.setReplicationFactor(indexInfo.getBasicInfo().getReplicationFactor());

        Map<String, FieldMeta> filedMetas = new LinkedHashMap<String, FieldMeta>(indexInfo.getFields().size() * 2);
        for (MetadataFieldQueryResult metadataField : indexInfo.getFields()) {
            FieldMeta fieldMeta = new FieldMeta();
            String name = metadataField.getFieldName();
            fieldMeta.setName(name);
            MetadataFieldType metadataFieldType = Enums.find(metadataField.getFieldTypeId(), MetadataFieldType.class)
                    .orElse(MetadataFieldType.STRING);
            fieldMeta.setType(metadataFieldType);
            fieldMeta.setPk(metadataField.isUniqueKey());
            fieldMeta.setMultiValued(metadataField.isMultiValued());
            fieldMeta.setRequired(metadataField.isRequired());
            fieldMeta.setIndexed(metadataField.isIndexed());

            if (metadataField.isUniqueKey()) {
                index.setPk(fieldMeta);
            }

            filedMetas.put(name, fieldMeta);
        }
        index.setFiledMetas(filedMetas);

        return index;
    }

    @Override
    public boolean register(IndexMeta indexMeta) {
        // TODO Auto-generated method stub
        return false;
    }
}
